/* --------------------------------------------------------
 * Copyright (c) aeky , Inc.  All rights reserved.
 * --------------------------------------------------------
 */
package com.aeky.server.support;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gwt.user.client.rpc.RemoteService;

/**
 * Utilities for internal use.
 */
public final class ReflectionUtils {

    /** The Constant LOG. */
    private static final Logger LOG = LoggerFactory.getLogger(ReflectionUtils.class);

    /**
     * prevent constructing an instance.
     */
    private ReflectionUtils() {
    }

    /**
     * Return array of all interfaces that are implemented by clazz and extend
     * {@link RemoteService}.
     * 
     * @param clazz Class Type
     * 
     * @return Array of interfaces. May be empty but never null.
     */
    @SuppressWarnings("unchecked")
    public static Class<RemoteService>[] getExposedInterfaces(Class<?> clazz) {
        Set<Class<?>> interfaces = getInterfaces(clazz);
        for (Iterator<Class<?>> ite = interfaces.iterator(); ite.hasNext();) {
            Class<?> c = ite.next();
            if (!isExposed(c)) {
                ite.remove();
            }
        }
        return interfaces.toArray(new Class[interfaces.size()]);
    }

    /**
     * Adds elements of an array to a set. The JRE 1.5 does include a similar
     * method in the Collections class, but that breaks GWT-SL 1.4
     * compatibility.
     * 
     * @param set set
     * @param elements elements
     */
    public static void addAll(Set<Class<?>> set, Class<?>[] elements) {
        for (Class<?> element : elements) {
            set.add(element);
        }
    }

    /**
     * Return all interfaces that are implemented by this class, traversing
     * super classes and super interfaces.
     * 
     * @param c class type
     * 
     * @return Set of classes. May be empty but not null.
     */
    public static Set<Class<?>> getInterfaces(Class<?> c) {
        Class<?>[] interfaces = c.getInterfaces();
        Set<Class<?>> classes = new HashSet<Class<?>>();
        if (interfaces == null) {
            return classes;
        }
        addAll(classes, interfaces);
        for (Class<?> cl : interfaces) {
            classes.addAll(getInterfaces(cl));
        }
        Class<?> superClass = c.getSuperclass();
        if (superClass != null) {
            classes.addAll(getInterfaces(superClass));
        }
        return classes;
    }

    /**
     * is exposed.
     * 
     * @param c Class
     * 
     * @return boolean
     */
    @SuppressWarnings("rawtypes")
    private static boolean isExposed(Class c) {
        return RemoteService.class.isAssignableFrom(c);
    }

    /**
     * Will try to find method in 'serviceInterfaces' and if found, will attempt
     * to return a method with the same signature from 'service', otherwise an
     * exception is thrown. If 'serviceInterfaces' is a zero-sized array, the
     * interface check is omitted and the method is looked up directly on the
     * object.
     * 
     * @param target Object to search method on
     * @param serviceInterfaces The requested method must exist on at least one
     *            of the interfaces
     * @param method method
     * 
     * @return Method on 'service' or else a {@link NoSuchMethodException} is
     *         thrown
     * 
     * @throws NoSuchMethodException NoSuchMethodException
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static Method getRPCMethod(Object target, Class[] serviceInterfaces, Method method)
            throws NoSuchMethodException {
        if (serviceInterfaces.length == 0) {
            return target.getClass().getMethod(method.getName(), method.getParameterTypes());
        }
        for (Class serviceInterface : serviceInterfaces) {
            try {
                Method template = serviceInterface.getMethod(method.getName(), method.getParameterTypes());
                return target.getClass().getMethod(template.getName(), template.getParameterTypes());
            } catch (NoSuchMethodException e) {
                LOG.debug(e.getMessage());
            }
        }
        throw new NoSuchMethodException(method.toString());
    }

    /**
     * This is from the AnnotationUtils class of the Springframework (2.5+) in
     * the core packag. This code is licensed under the Apache v2 license.
     * 
     * @param clazz Class
     * @param <A> annotation class
     * @param annotationType annotationType
     * 
     * @return annotation
     */
    public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
        A annotation = clazz.getAnnotation(annotationType);
        if (annotation != null) {
            return annotation;
        }
        for (Class<?> ifc : clazz.getInterfaces()) {
            annotation = findAnnotation(ifc, annotationType);
            if (annotation != null) {
                return annotation;
            }
        }
        Class<?> superClass = clazz.getSuperclass();
        if (superClass == null || superClass == Object.class) {
            return null;
        }
        return findAnnotation(superClass, annotationType);
    }

}
